//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: SqlQueryBuilder.cs
//
//  Description: Creates instances of SqlQuery classes.
// 
//--------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using Chili.Opf3.Query;

namespace Chili.Opf3.Query
{
	/// <summary>
	/// A class that creates an instance of an <see cref="SqlQuery">SqlQuery</see> class.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This class creates an instance of an <see cref="SqlQuery">SqlQuery</see> class. It allows 
	/// to specify the different items that can be added to an SqlQuery. For more information about the 
	/// SqlQuery, please consult the documentation of that class.
	/// </para>
	/// </remarks>
	/// <example>
	/// The following example shows how to use SqlQueryBuilder in your application.
	/// <code lang="CS">
	/// // ... Other code
	/// 
	/// SqlQueryBuilder sqb = new SqlQueryBuilder("select * from USERS");
	/// sqb.AddConditionIfParameterNotNull("lower(LAST_NAME) like {0}", lastName);
	/// sqb.AddConditionIfParameterNotNull("lower(FIRST_NAME) like {0}", firstName);
	///
	/// ObjectSet&lt;Call&gt; os = context.GetObjectSet&lt;Call&gt;(sqb.ToSqlQuery());
	/// 
	/// // ... Other code.
	/// </code>
	/// </example>
	public sealed class SqlQueryBuilder
	{
		private List<object> _parameters = new List<object>();
		private string _conditions = string.Empty;
		private string _selectPart;
        private string _groupByPart;
		private string _orderByPart;
		private Concats _defaultConcat = Concats.And;

		/// <summary>
		/// Creates a new instance of the <see cref="SqlQueryBuilder">SqlQueryBuilder Class</see>.
		/// </summary>
		/// <param name="selectPart">The select part of the query.</param>
		public SqlQueryBuilder(string selectPart)
		{
			this.SelectPart = selectPart;
		}

		/// <summary>
		/// Appends a condition to the query. The parameter position is specified by the {0}, {1}, {2}, ... strings.
		/// </summary>
		/// <param name="condition">The conditional string that may also contain a parameter.</param>
		/// <param name="parameters">The list of parameter values.</param>
		public void AppendCondition(string condition, params object[] parameters)
		{
			if (condition == null)
				throw new ArgumentNullException("condition");

			CheckForDefaultConcat();

			if (parameters != null)
			{
				// Replace only if there are already conditions.
				if (_conditions.Length > 0)
				{
					// Loop over all parameters.
					for (int i = parameters.Length - 1; i >= 0; i--)
					{
						// Replace the parameter index with the global index.
						condition = condition.Replace("{" + i + "}", "{" + (_parameters.Count + i) + "}");
					}
				}

				// Add the parameter to the list of parameters.
				_parameters.AddRange(parameters);
			}

			// Add the condition to the conditions string.
			_conditions = string.Format("{0} {1} ", _conditions, condition);
		}

		/// <summary>
		/// Appends one or more parameter-less conditions to the query. Do not append any concat at the end.
		/// </summary>
		/// <param name="condition">The conditional string that may also contain a parameter.</param>
		public void AppendCondition(string condition)
		{
			AppendCondition(condition, null);
		}

		/// <summary>
		/// Adds a condition to the query. The condition is only added if the paremeter is not null. The parameter
		/// position is specified by the {0} string.
		/// </summary>
		/// <param name="condition">The conditional string that may also contain a parameter.</param>
		/// <param name="parameter">The parameter value.</param>
		public void AppendConditionIfParameterNotNull(string condition, object parameter)
		{
			if (condition == null)
				throw new ArgumentNullException("condition");

			if (parameter == null)
				return;
			AppendCondition(condition, parameter);
		}

		/// <summary>
		/// Checks if to add a default concat between two items of the query.
		/// </summary>
		private void CheckForDefaultConcat()
		{
			string c = _conditions.Trim();
			if (_conditions == string.Empty)
				return;
			if (!c.EndsWith(Concats.And.ToString()) && !c.EndsWith(Concats.Or.ToString()) && !c.EndsWith("("))
				this.AppendConcat(this.DefaultConcat);
		}

		/// <summary>
		/// Specifies the default concat that is used if <see cref="ObjectQueryBuilder{T}.AppendProperty">AppendProperty</see> is 
		/// called more then one time without calling <see cref="ObjectQueryBuilder{T}.AppendConcat">AppendConcat</see>.
		/// </summary>
		public Concats DefaultConcat
		{
			get { return _defaultConcat; }
			set { _defaultConcat = value; }
		}

		/// <summary>
		/// Appends a concat to the query.
		/// </summary>
		/// <param name="concat">The concat that is going to be added to the query.</param>
		public void AppendConcat(Concats concat)
		{
			_conditions += concat.ToString();
		}

		/// <summary>
		/// Appends an opening parenthesis to the query.
		/// </summary>
		public void AppendOpeningParenthesis()
		{
			CheckForDefaultConcat();
			_conditions += " (";
		}

		/// <summary>
		/// Appends a closing parenthesis to the query.
		/// </summary>
		public void AppendClosingParenthesis()
		{
			_conditions += ") ";
		}

		/// <summary>
		/// Returns whether the current instance of the class is empty.
		/// </summary>
		public bool HasConditions
		{
			get { return (_parameters.Count > 0); }
		}

		/// <summary>
		/// Clears the current condtions of the <see cref="SqlQueryBuilder">SqlQueryBuilder</see>.
		/// </summary>
		public void ClearConditions()
		{
			_conditions = string.Empty;
			_parameters.Clear();
		}

		/// <summary>
		/// Gets or sets the select part of the query.
		/// </summary>
		public string SelectPart
		{
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				_selectPart = value;
			}
			get
			{
				return _selectPart;
			}
		}

        /// <summary>
        /// Gets or sets the group by part of the query.
        /// </summary>
        public string GroupByPart
        {
            set
            {
                _groupByPart = value;
            }
            get
            {
                return _groupByPart;
            }
        }

		/// <summary>
		/// Gets or sets the order by part of the query.
		/// </summary>
		public string OrderByPart
		{
			set
			{
				_orderByPart = value;
			}
			get
			{
				return _orderByPart;
			}
		}

		/// <summary>
		/// Converts the value to a instance of the <see cref="SqlQuery">SqlQuery</see> Class.
		/// </summary>
		public SqlQuery ToSqlQuery()
		{
			string query = _selectPart;
			if (_conditions != string.Empty)
				query += " where " + _conditions;

            if(!String.IsNullOrEmpty(_groupByPart))
                query += " " + _groupByPart;

            if (!String.IsNullOrEmpty(_orderByPart))
			    query += " " + _orderByPart;

			return new SqlQuery(query, _parameters.ToArray());
		}
	}
}
